#include "WWebsocketClient.h"
#include <functional>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "WLog.h"

#define RECONNECT_TIME_MS 10000

WWebsocketClient::WWebsocketClient(mg_mgr *websocketMgr)
{
    _websocketMgr = websocketMgr;
    if (!_websocketMgr)
    {
        _websocketMgr = new mg_mgr();
        mg_mgr_init(_websocketMgr);
        _selfMgrFlag = true;
    }
}

WWebsocketClient::~WWebsocketClient()
{
    if (_websocketConnection)
    {
        close_conn(_websocketConnection);
        _websocketConnection = nullptr;
    }

    disconnect();

    if (_startReConnectTimerFlag)
    {
        mg_timer_free(&_reConnectTimer);
    }

    if (_selfMgrFlag)
    {
        mg_mgr_free(_websocketMgr);
        delete _websocketMgr;
        _websocketMgr = nullptr;
    }
}

bool WWebsocketClient::connect(string url)
{
    innerDisconnect();

    struct mg_str host = mg_url_host(url.c_str());
    string dnsStr = "";
    dnsStr.resize(host.len);
    memcpy((char *)dnsStr.c_str(), host.ptr, host.len);
    if (dnsStr.find(":") == string::npos)
    {
        string ipStr = getIpFromDNS(dnsStr);
        size_t index = url.find(dnsStr);
        url.replace(index, dnsStr.size(), ipStr);
        Logi("WWebsocketClient::Connect dnsStr is %s, ip is %s, url after replace is %s", dnsStr.c_str(), ipStr.c_str(), url.c_str());
    }
    else
    {
        Logi("WWebsocketClient::Connect use ipv6 url: %s", url.c_str());
    }

    _websocketConnection = mg_ws_connect(_websocketMgr, url.c_str(), &WWebsocketClient::receiveMsg, (void*)this, nullptr);
    if (!_websocketConnection)
    {
        Logi("WWebsocketClient::Connect connect ws server error!");
        return false;
    }

    _url = url;
    _workFlag = true;
    return true;
}

void WWebsocketClient::reConnect()
{
    _reConnectCount++;
    Logi("WWebsocketClient::ReConnect count is %d", _reConnectCount);
    connect(_url);
}

bool WWebsocketClient::disconnect()
{
    innerDisconnect();
    _workFlag = false;
    return true;
}

void WWebsocketClient::innerDisconnect()
{
    if (_websocketConnection)
    {
        _websocketConnection->is_closing = 1;
        _websocketConnection = nullptr;
    }
    _url = "";
}

void WWebsocketClient::receiveMsg(struct mg_connection *connection, int dataType, void *data, void *cbData)
{
    WWebsocketClient *wsClient = (WWebsocketClient *)cbData;
    wsClient->onReceiveMsg(connection, dataType, data);
}

void WWebsocketClient::reconnectTimerEvent(void *arg)
{
    WWebsocketClient *wsClient = (WWebsocketClient *)arg;
    wsClient->reConnect();
}

string WWebsocketClient::getIpFromDNS(const string &dnsStr, bool isIpv6First)
{
    std::string ipv6Str = "";
    std::string ipv4Str = "";

    struct addrinfo hints;
    struct addrinfo *result, *rp;

    memset(&hints, 0, sizeof(struct addrinfo));
    // hints.ai_family = AF_INET; // 只返回ipv4地址
    hints.ai_family = AF_UNSPEC; // ipv4、ipv6地址都返回
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;          /* Any protocol */

    int s = getaddrinfo(dnsStr.c_str(), nullptr, &hints, &result);
    if (s != 0)
    {
        Loge("getaddrinfo error: %s, %s", dnsStr.c_str(), gai_strerror(s));
        return "";
    }

    char serverIp[46] = {0};
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        if (rp->ai_family == AF_INET6)
        {
            sockaddr_in6 *sourceaddr = (sockaddr_in6 *)rp->ai_addr;
            inet_ntop(sourceaddr->sin6_family, &(sourceaddr->sin6_addr), serverIp, 46);
            if (ipv6Str.empty())
            {
                ipv6Str = serverIp;
            }
        }
        else
        {
            sockaddr_in* addr_in = (sockaddr_in*)rp->ai_addr;
            inet_ntop(addr_in->sin_family, &addr_in->sin_addr, serverIp, 16);
            if(ipv4Str.empty())
            {
                ipv4Str = serverIp;
            }
        }
    }

    freeaddrinfo(result);

    if (isIpv6First)
    {
        return ipv6Str.empty() ? ipv4Str : ipv6Str;
    }
    else
    {
        return ipv4Str.empty()? ipv6Str : ipv4Str;
    }
}

void WWebsocketClient::onReceiveMsg(struct mg_connection *connection, int dataType, void *data)
{
    switch(dataType)
    {
        case MG_EV_ERROR:
        {
            Loge("WWebsocketClient::OnReceiveMsg websocket client error: %s", (char *)data);
            _connectFlag = false;
            if (_autoReconnectFlag && !_startReConnectTimerFlag && _workFlag)
            {
                _startReConnectTimerFlag = true;
                mg_timer_init(&_reConnectTimer, RECONNECT_TIME_MS, MG_TIMER_REPEAT, &WWebsocketClient::reconnectTimerEvent, this);
            }

            if (_listener)
            {
                _listener->onWsClientError();
            }

            break;
        }
        case MG_EV_CLOSE:
        {
            Logi("WWebsocketClient::OnReceiveMsg websocket client closed: %s", _url.c_str());
            _connectFlag = false;
            _websocketConnection = nullptr;
            if (_autoReconnectFlag && !_startReConnectTimerFlag && _workFlag)
            {
                _startReConnectTimerFlag = true;
                mg_timer_init(&_reConnectTimer, RECONNECT_TIME_MS, MG_TIMER_REPEAT, &WWebsocketClient::reconnectTimerEvent, this);
            }

            if (_listener)
            {
                _listener->onWsClientClosed();
            }

            break;
        }
        case MG_EV_WS_OPEN:
        {
            _reConnectCount = 0;
            _connectFlag = true;
            if (_startReConnectTimerFlag)
            {
                _startReConnectTimerFlag = false;
                mg_timer_free(&_reConnectTimer);
            }

            if (_listener)
            {
                _listener->onWsClientConnected();
            }
            Logi("WWebsocketClient::OnReceiveMsg websocket client connect success: %s", _url.c_str());
            break;
        }
        case MG_EV_CONNECT:
        {
            // if wss, use ssl
            if (mg_url_is_ssl(_url.c_str()))
            {
                struct mg_tls_opts opts;
                opts.ca = nullptr;
                opts.cert = nullptr;
                opts.certkey = nullptr;
                opts.ciphers = nullptr;
                opts.srvname.ptr = nullptr;
                opts.srvname.len = 0;
                mg_tls_init(connection, &opts);
            }
            break;
        }
        case MG_EV_WS_MSG:
        {
            struct mg_ws_message *wm = (struct mg_ws_message *) data;
            if (mg_ncasecmp(wm->data.ptr, "ping", 4) == 0) // 有的端是用text的方式发送ping
            {
                sendMsg("PONG");
            }
            else
            {
                string msg(wm->data.ptr, wm->data.len);
                Logi("WWebsocketClient::OnReceiveMsg msg info: %s", msg.c_str());
                if (_listener)
                {
                    _listener->onWsClientReceiveMsg(msg);
                }
            }

            break;
        }
        default:
        {
            break;
        }
    }
}

bool WWebsocketClient::sendMsg(const string &msg)
{
    if (!_connectFlag || !_websocketConnection)
    {
        Logi("WWebsocketClient::SendMsg websocket client do not connect");
        return false;
    }
    mg_ws_send(_websocketConnection, msg.c_str(), msg.size(), WEBSOCKET_OP_TEXT);
    return true;
}

void WWebsocketClient::setListener(IWebsocketClientCb *listener)
{
    _listener = listener;
}

bool WWebsocketClient::run()
{
    mg_mgr_poll(_websocketMgr, 1);
    return true;
}

bool WWebsocketClient::isConnected()
{
    return _connectFlag;
}

void WWebsocketClient::setAutoReconnect(bool flag)
{
    _autoReconnectFlag = flag;
}
